using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

/// <summary>
/// Settings in ini format
/// </summary>
public class SimpleSettings
{
	#region Constructors

	private readonly bool caseInvariant;

	/// <summary>
	/// Default Constructor, case invariant = true
	/// </summary>
	public SimpleSettings()
		: this(true)
	{
	}

	/// <summary>
	/// Constructor
	/// </summary>
	/// <param name="caseInvariant"></param>
	public SimpleSettings(bool caseInvariant)
	{
		this.caseInvariant = caseInvariant;
		GlobalSection = CreateDictionary();
		if (!caseInvariant)
			Sections = new Dictionary<string, Dictionary<string, string>>();
		else
			Sections = new Dictionary<string, Dictionary<string, string>>(ignoreCaseStringComparer);

	}

	#endregion

	#region Public Fields/Properties

	/// <summary>
	/// Full file name (is set by Load function)
	/// </summary>
	public string FullFileName { get; private set; }

	/// <summary>
	/// Settings not in sections
	/// </summary>
	public Dictionary<string, string> GlobalSection { get; private set; }

	/// <summary>
	/// Sections of settings
	/// </summary>
	public Dictionary<string, Dictionary<string, string>> Sections { get; private set; }

	#endregion

	#region Load/Save

	/// <summary>
	/// Clear all data
	/// </summary>
	public void Clear()
	{
		GlobalSection.Clear();
		Sections.Clear();
	}

	/// <summary>
	/// Load settings from file
	/// </summary>
	/// <param name="fileName"></param>
	/// <returns></returns>
	public virtual bool Load(string fileName)
	{
		try
		{
			bool result;
			using (FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				StreamReader sr = new StreamReader(fs);
				FullFileName = fs.Name;
				result = LoadFromStream(sr);
				sr.Close();
			}
			return result;
		}
		catch (Exception)
		{
			return false;
		}
	}

	/// <summary>
	/// Load settings from data
	/// </summary>
	/// <param name="data"></param>
	/// <returns></returns>
	public bool LoadFromData(string data)
	{
		return LoadFromStream(new StringReader(data));
	}

	/// <summary>
	/// Load settings from stream
	/// </summary>
	/// <param name="reader"></param>
	/// <returns></returns>
	public bool LoadFromStream(TextReader reader)
	{
		Clear();
		Dictionary<string, string> currentSection = GlobalSection;
		while (reader.Peek() != -1)
		{
			string line = reader.ReadLine().Trim();
			if (string.IsNullOrEmpty(line) || line.StartsWith("'"))
				continue;
			if (line.StartsWith("[") && line.EndsWith("]"))
			{
				string sectionName = line.Substring(1, line.Length - 2).Trim();
				currentSection = CreateDictionary();
				Sections.Add(sectionName, currentSection);
			}
			else
			{
				string name, value;
				int k = line.IndexOf('=');
				if (k == -1)
				{
					name = line.Trim();
					value = "";
				}
				else
				{
					name = line.Substring(0, k).Trim();
					value = line.Substring(k + 1).Trim();
				}
				if (!CheckItemNameAllowed(currentSection, name, currentSection == GlobalSection))
					return false;
				currentSection.Add(name, value);
			}
		}
		return true;
	}

	/// <summary>
	/// Save settings
	/// </summary>
	/// <returns></returns>
	public bool Save()
	{
		return Save(FullFileName);
	}

	/// <summary>
	/// Save settings
	/// </summary>
	/// <param name="fileName"></param>
	/// <returns></returns>
	public virtual bool Save(string fileName)
	{
		try
		{
			using (FileStream fs = File.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				StreamWriter sw = new StreamWriter(fs);
				foreach (KeyValuePair<string, string> pair in GlobalSection)
				{
					sw.WriteLine(pair.Key + "=" + pair.Value);
				}
				foreach (KeyValuePair<string, Dictionary<string, string>> pair in Sections)
				{
					sw.WriteLine("[" + pair.Key + "]");
					foreach (KeyValuePair<string, string> keyValuePair in pair.Value)
					{
						sw.WriteLine(keyValuePair.Key + "=" + keyValuePair.Value);
					}
				}
				sw.Close();
			}
			return true;
		}
		catch (Exception)
		{
			return false;
		}
	}

	#endregion

	#region Auto Check on Load

	/// <summary>
	/// List of names allowed for items in global section
	/// </summary>
	public List<string> GlobalSectionAllowedItemNames { get; set; }

	/// <summary>
	/// List of names allowed for items in sections
	/// </summary>
	public List<string> SectionsAllowedItemNames { get; set; }

	/// <summary>
	/// Check if item name is allowed
	/// </summary>
	/// <param name="section"></param>
	/// <param name="name"></param>
	/// <param name="isGlobalSection"></param>
	/// <returns></returns>
	protected virtual bool CheckItemNameAllowed(Dictionary<string, string> section, string name, bool isGlobalSection)
	{
		if (string.IsNullOrEmpty(name) || section.ContainsKey(name))
			return false;
		List<string> allowedNames = isGlobalSection ? GlobalSectionAllowedItemNames : SectionsAllowedItemNames;
		if (allowedNames == null)
			return true;
		if (!caseInvariant)
			return allowedNames.Contains(name);
		foreach (var allowedName in allowedNames)
		{
			if (ignoreCaseStringComparer.Equals(name, allowedName))
				return true;
		}
		return false;
	}

	#endregion

	#region Get/Set values and related

	/// <summary>
	/// Check if Global section contains specified key
	/// </summary>
	/// <param name="key"></param>
	/// <returns></returns>
	public bool ContainsKey(string key)
	{
		return GlobalSection.ContainsKey(key);
	}

	/// <summary>
	/// Check if specified section contains specified key
	/// </summary>
	/// <param name="key"></param>
	/// <param name="section"></param>
	/// <returns></returns>
	public bool ContainsKey(string key, string section)
	{
		return Sections.ContainsKey(section) && Sections[section].ContainsKey(key);
	}

	/// <summary>
	/// Get value
	/// </summary>
	/// <param name="section"></param>
	/// <param name="key"></param>
	/// <param name="defaultValue"></param>
	/// <returns></returns>
	public string GetValue(string section, string key, string defaultValue)
	{
		return ContainsKey(key, section) ? Sections[section][key] : defaultValue;
	}

	/// <summary>
	/// Get value
	/// </summary>
	/// <param name="key"></param>
	/// <param name="defaultValue"></param>
	/// <returns></returns>
	public string GetValue(string key, string defaultValue)
	{
		return ContainsKey(key) ? GlobalSection[key] : defaultValue;
	}

	#endregion

	#region Implementation

	private static readonly IgnoreCaseStringComparer ignoreCaseStringComparer = new IgnoreCaseStringComparer();

	private Dictionary<string, string> CreateDictionary()
	{
		if (!caseInvariant)
			return new Dictionary<string, string>();
		return new Dictionary<string, string>(ignoreCaseStringComparer);
	}

	#endregion
}
